"""
AST Parser using libclang

Parses C++ source files and provides AST traversal functionality
for template instantiation analysis.
"""

import os
import sys
from pathlib import Path
from typing import Optional, List, Dict, Any, Tuple, TYPE_CHECKING
from dataclasses import dataclass

# Import clang types for type annotations only
if TYPE_CHECKING:
    from clang.cindex import TranslationUnit, Cursor, CursorKind

# Don't import clang at module level - import it when needed in methods
CLANG_AVAILABLE = None  # Will be determined at runtime


@dataclass
class SourcePosition:
    """Represents a position in source code."""
    file_path: str
    line: int
    column: int
    
    def __post_init__(self):
        # Normalize file path
        self.file_path = str(Path(self.file_path).resolve())


class ASTParser:
    """Parses C++ source files using libclang."""
    
    def _is_clang_available(self):
        """Check if clang is available at runtime."""
        try:
            # Always try fresh import - don't cache failures since libclang might be configured later
            from clang.cindex import Index
            # Test that we can actually create an index (this will fail if libclang isn't configured)
            Index.create()
            return True
        except Exception:
            return False
    
    def __init__(self):
        # Check clang availability at runtime
        if not self._is_clang_available():
            error_msg = "libclang is not available. Please ensure libclang is configured before importing TIR modules.\n"
            error_msg += "Run 'python -m tir.libclang_config' to check configuration status."
            raise ImportError(error_msg)
        
        # Import clang types now that we know they're available
        from clang.cindex import Index, TranslationUnit, Cursor, CursorKind, SourceLocation, File
        
        self.index = Index.create()
        self.translation_unit: Optional["TranslationUnit"] = None
        self.current_file_path: Optional[str] = None
    
    def parse_file(self, file_path: str, compile_args: List[str] = None) -> bool:
        """Parse a C++ source file."""
        file_path = str(Path(file_path).resolve())
        
        if not os.path.exists(file_path):
            raise FileNotFoundError(f"Source file not found: {file_path}")
        
        # Default compilation arguments with system include paths
        if compile_args is None:
            compile_args = [
                '-std=c++17',
                '-resource-dir=/usr/lib/clang/19',
                '-I/usr/lib/gcc/x86_64-pc-linux-gnu/14/include/g++-v14',
                '-I/usr/lib/gcc/x86_64-pc-linux-gnu/14/include/g++-v14/x86_64-pc-linux-gnu',
                '-I/usr/lib/gcc/x86_64-pc-linux-gnu/14/include/g++-v14/backward',
                '-I/usr/local/include',
                '-I/usr/include'
            ]
        
        try:
            # Import TranslationUnit at runtime
            from clang.cindex import TranslationUnit
            
            self.translation_unit = self.index.parse(
                file_path,
                compile_args,
                options=TranslationUnit.PARSE_DETAILED_PROCESSING_RECORD |
                       TranslationUnit.PARSE_SKIP_FUNCTION_BODIES
            )
            
            self.current_file_path = file_path
            
            # Check for parse errors
            if self.translation_unit is None:
                return False
            
            # Log diagnostics for debugging
            has_errors = False
            for diag in self.translation_unit.diagnostics:
                if diag.severity >= 3:  # Error level
                    print(f"Parse error: {diag}", file=sys.stderr)
                    has_errors = True
                elif diag.severity == 2:  # Warning level
                    print(f"Parse warning: {diag}", file=sys.stderr)
            
            return not has_errors
            
        except Exception as e:
            print(f"Failed to parse file {file_path}: {e}", file=sys.stderr)
            return False
    
    def find_cursor_at_position(self, position: SourcePosition) -> "Optional[Cursor]":
        """Find the AST cursor at the specified source position."""
        if not self.translation_unit:
            return None
        
        # Import clang types at runtime
        from clang.cindex import Cursor, SourceLocation, File
        
        # Get the file object for the specified position
        file_obj = File.from_name(self.translation_unit, position.file_path)
        if not file_obj:
            return None
        
        # Get source location
        source_location = SourceLocation.from_position(
            self.translation_unit, 
            file_obj, 
            position.line, 
            position.column
        )
        
        if not source_location:
            return None
        
        # Get cursor at location
        cursor = Cursor.from_location(self.translation_unit, source_location)
        return cursor
    
    def find_cursor_by_name(self, name: str, cursor_kinds: "List[CursorKind]" = None) -> "List[Cursor]":
        """Find cursors by name, optionally filtering by cursor kinds."""
        if not self.translation_unit:
            return []
        
        # Import CursorKind at runtime
        from clang.cindex import CursorKind
        
        if cursor_kinds is None:
            cursor_kinds = [CursorKind.VAR_DECL, CursorKind.PARM_DECL, CursorKind.FIELD_DECL]
        
        results = []
        
        def visit_cursor(cursor: "Cursor"):
            if (cursor.spelling == name and 
                cursor.kind in cursor_kinds and
                self._is_cursor_in_main_file(cursor)):
                results.append(cursor)
            
            # Recursively visit children
            for child in cursor.get_children():
                visit_cursor(child)
        
        # Start traversal from root
        visit_cursor(self.translation_unit.cursor)
        return results
    
    def _is_cursor_in_main_file(self, cursor: "Cursor") -> bool:
        """Check if cursor is in the main file being analyzed."""
        if not cursor.location or not cursor.location.file:
            return False
        
        cursor_file_path = str(Path(cursor.location.file.name).resolve())
        return cursor_file_path == self.current_file_path
    
    def get_cursor_context(self, cursor: "Cursor", context_lines: int = 3) -> Dict[str, Any]:
        """Get context information around a cursor."""
        if not cursor.location:
            return {}
        
        location = cursor.location
        
        context = {
            'file': location.file.name if location.file else 'unknown',
            'line': location.line,
            'column': location.column,
            'kind': cursor.kind.name,
            'spelling': cursor.spelling,
            'type_spelling': cursor.type.spelling if cursor.type else 'unknown',
            'semantic_parent': cursor.semantic_parent.spelling if cursor.semantic_parent else None,
            'lexical_parent': cursor.lexical_parent.spelling if cursor.lexical_parent else None
        }
        
        # Add source code snippet if available
        try:
            if location.file:
                file_path = location.file.name
                with open(file_path, 'r') as f:
                    lines = f.readlines()
                    
                start_line = max(0, location.line - context_lines - 1)
                end_line = min(len(lines), location.line + context_lines)
                
                context['source_lines'] = []
                for i in range(start_line, end_line):
                    line_num = i + 1
                    line_content = lines[i].rstrip()
                    is_target_line = (line_num == location.line)
                    context['source_lines'].append({
                        'line_number': line_num,
                        'content': line_content,
                        'is_target': is_target_line
                    })
        
        except Exception as e:
            context['source_error'] = str(e)
        
        return context
    
    def traverse_ast(self, cursor: "Cursor" = None, max_depth: int = -1, current_depth: int = 0) -> Dict[str, Any]:
        """Traverse the AST and return a structured representation."""
        if cursor is None:
            cursor = self.translation_unit.cursor if self.translation_unit else None
        
        if not cursor or (max_depth >= 0 and current_depth > max_depth):
            return {}
        
        node_info = {
            'kind': cursor.kind.name,
            'spelling': cursor.spelling,
            'type': cursor.type.spelling if cursor.type else None,
            'location': {
                'file': cursor.location.file.name if cursor.location and cursor.location.file else None,
                'line': cursor.location.line if cursor.location else None,
                'column': cursor.location.column if cursor.location else None,
            },
            'children': []
        }
        
        # Recursively process children
        for child in cursor.get_children():
            child_info = self.traverse_ast(child, max_depth, current_depth + 1)
            if child_info:
                node_info['children'].append(child_info)
        
        return node_info
    
    def get_diagnostics(self) -> List[Dict[str, Any]]:
        """Get parsing diagnostics."""
        if not self.translation_unit:
            return []
        
        diagnostics = []
        for diag in self.translation_unit.diagnostics:
            diag_info = {
                'severity': diag.severity,
                'location': {
                    'file': diag.location.file.name if diag.location.file else None,
                    'line': diag.location.line,
                    'column': diag.location.column,
                },
                'spelling': diag.spelling,
                'category': diag.category_name
            }
            diagnostics.append(diag_info)
        
        return diagnostics
    
    def cleanup(self):
        """Cleanup resources."""
        self.translation_unit = None
        self.current_file_path = None